class Vue {
   constructor(options) {
      this.$options = options
      this._data = options.data
      this.initData()
   }
   initData() {
      let data = this._data
      let keys = Object.keys(data)
      for(let i=0;i<keys.length;i++) {
         Object.defineProperty(this, keys[i], {
            enumerable: true,
            configurable: true,
            get() {
               return data[keys[i]]
            },
            set(value) {
               data[keys[i]] = value
            }
         })
      }
      observe(data)
      this.initWatch()
   }
   initWatch() {
      let watch = this.$options.watch
      if(watch) {
         let keys = Object.keys(watch)
         for(let i=0;i<keys.length; i++) {
            new Watcher(this, keys[i], watch[keys[i]])
         }
      }
   }
   $watch(exp, cb) {
      new Watcher(this, exp, cb)
   }
}

function observe(data) {
   let type = Object.prototype.toString.call(data)
   if(type !== '[object Object]' && type!=='[object Array]') return 
   new Observer(data) 
}

function defineReactive(obj, key, value) {
   observe(obj[key])
   let dep = new Dep()
   Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
         dep.depend()
         return value
      },
      set(val) {
         if(val === value) return
         value = val
         dep.nofity()
      }
   })
}

class Observer {
   constructor(data) {
      this.walk(data) 
   }
   walk(data) {
      let keys = Object.keys(data)
      for(let i=0;i<keys.length;i++) {
         defineReactive(data, keys[i], data[keys[i]])
      }
   }
}

class Dep {
   constructor() {
      this.subs = []
   }
   depend() {
      if(Dep.target) {
         this.subs.push(Dep.target)
      }
   }
   nofity() {
      this.subs.forEach((watcher) => {
         watcher.run()
      })
   }
}

let watcherId = 0
let watcherQueue = []

class Watcher {
   constructor(vm, exp, cb) {
      this.vm = vm
      this.exp = exp
      this.cb = cb
      this.id = ++watcherId
      this.get()
   }

   get() {
      Dep.target = this
      this.vm[this.exp]
      Dep.target = undefined
   }

   run() {
      if(watcherQueue.indexOf(watcherId) !== -1) return
      watcherQueue.push(this.id)
      let index = watcherQueue.length -1
      Promise.resolve().then(() => {
         this.cb.call(this.vm)
         watcherQueue.splice(index, 1) // 这一句应当放在 then 函数当中，否则 index 会被缓存(会不会和闭包有关系)
      })      
   }
}